#pragma once

#include <iostream>
#include <string>
#include <queue>
#include <pthread.h>
#include <unistd.h> 
#include <ctime>

const static int defaultcap = 5;

template<typename T>
class blockQueue
{
private:
    bool isFull()
    {
        return _block_queue.size() == _capacity;
    }

    bool isEmpty()
    {
        return _block_queue.empty();
    }
public:
    blockQueue(int capacity = defaultcap)
        :_capacity(capacity)
        // ,highlevel((defaultcap / 3) * 2)
        // ,lowlevel(defaultcap / 3)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_pcond, nullptr);
        pthread_cond_init(&_ccond, nullptr);
    }

    void dequeue(T* out)
    {
        pthread_mutex_lock(&_mutex);
        // 这里之所以利用 while 进行判断
        // 是为了防止下面的情况发生
        // 假设有一个生产线程 和 两个消费线程
        // 再假设生产线程在阻塞队列中放入一个数据后，使用pthread_cond_broadcast进行唤醒
        // 那么两个消费线程都会在下面的代码块中的语句直接继续往下执行
        // 第一个抢到锁的消费线程可以拿走数据，但第二抢到锁的消费线程拿不到数据
        // 所以此时需要利用 while，即使是从代码块中的语句唤醒，仍然会进行判空操作
        while (isEmpty())
        {
            pthread_cond_wait(&_ccond, &_mutex);
        }
        *out = _block_queue.front();
        _block_queue.pop();
        // if (_block_queue.size() > highlevel)
        // {
        //     pthread_cond_signal(&_pcond);
        // }
        pthread_mutex_unlock(&_mutex);
        // 消费者消费了一次之后，阻塞队列一定不满，
        // 此时一定可以通过_pcond这个生产者一直在等的条件变量通知生产者可以进行生产了
        pthread_cond_signal(&_pcond);
    }

    void enqueue(const T& in)
    {
        pthread_mutex_lock(&_mutex);
        while (isFull())
        {
            //队列已满，生产者线程必须等待
            //在等待时，这段临界区代码就不能一直拥有_mutex这个锁了
            // 此时向pthread_cond_wait这个函数中传入 _mutex 这个参数就是为了将锁进行暂时的释放
            //在等待结束后，pthread_cond_wait将会返回，
            // 在返回之前，这个函数会竞争锁，竞争成功加上锁之后，才会返回这个函数   
            pthread_cond_wait(&_pcond, &_mutex);
        }
        _block_queue.push(in);
        // if (_block_queue.size() < lowlevel)
        // {
        //     pthread_cond_signal(&_ccond);
        // }
        pthread_mutex_unlock(&_mutex);
        pthread_cond_signal(&_ccond);
        // 让消费者消费

    }

    ~blockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_pcond);
        pthread_cond_destroy(&_ccond);
    }

private:
    // 下面这个 queue 类型的对象将会是超市里的资源，
    // 由于生产线程和消费线程都会访问这个资源，所以要对这个对象进行保护
    std::queue<T> _block_queue; 
    pthread_mutex_t _mutex;
    int _capacity;
    pthread_cond_t _pcond; // 生产者条件变量
    pthread_cond_t _ccond; // 消费者条件变量
    // int highlevel;
    // int lowlevel;
};
